﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleActor.Test
{
    public class LogRecord
    {
        private readonly int id;
        private readonly DateTime timestamp;
        private readonly string message;

        public LogRecord(int id, DateTime timestamp, string message)
        {
            this.id = id;
            this.timestamp = timestamp;
            this.message = message;
        }

        public int ID { get { return id; } }
        public DateTime Timestamp { get { return timestamp; } }
        public string Message { get { return message; } }
    }

    public interface ISampleLogger
    {
        Task<int> Log(DateTime timestamp, string message);
        Task<int> LogDelayed(DateTime timestamp, string message, TimeSpan delay);

        Task LogStat();

        IObservable<LogRecord> Logged { get; }
    }

    public interface ISampleLoggingService :  ISampleLogger, IActor
    {
    }

    internal interface ISampleLoggingServant : ISampleLogger, IActorServant
    {
    }

    public static class SampleServiceFactory
    {
        public static ISampleLoggingService Create()
        {
            var servant = new SampleLoggingServant();
            var scheduler = new MessageScheduler<ISampleLoggingServant>("Service", servant);
            var proxy = new SampleLoggingServiceProxy(scheduler);
            return proxy;
        }
    }

    internal class SampleLoggingServiceProxy : ActorProxyBase<ISampleLoggingServant>, ISampleLoggingService
    {
        public SampleLoggingServiceProxy(IMessageScheduler<ISampleLoggingServant> scheduler)
            : base(scheduler)
        {
        }

        public Task<int> Log(DateTime timestamp, string message)
        {
            var func = Scheduler.GetQueueFunc<DateTime, string, int>((s, p1, p2) => s.Log(p1, p2));
            var ret = func(timestamp, message);
            return ret;
        }

        public Task<int> LogDelayed(DateTime timestamp, string message, TimeSpan delay)
        {
            var func = Scheduler.GetQueueFunc<DateTime, string, TimeSpan, int>((s, p1, p2, p3) => s.LogDelayed(p1, p2, p3));
            var ret = func(timestamp, message, delay);
            return ret;
        }

        public Task LogStat()
        {
            var func = Scheduler.GetQueueAction(x => x.LogStat());
            var ret = func();
            return ret;
        }

        public IObservable<LogRecord> Logged
        {
            get { return Scheduler.GetObservable(s => s.Logged); }
        }
    }

    internal class SampleLoggingServant : ActorServantBase, ISampleLoggingServant
    {
        private static readonly TimeSpan Period = TimeSpan.FromSeconds(3);
        private static readonly TimeSpan Wait = TimeSpan.FromMilliseconds(100);
        private static readonly TimeSpan LongWait = TimeSpan.FromMilliseconds(500);

        private static void DoSomething()
        {
            Thread.Sleep(Wait);
        }

        private static void DoSomethingLong()
        {
            Thread.Sleep(LongWait);
        }

        private readonly Subject<LogRecord> logged;
        private int count;

        public SampleLoggingServant()
        {
            this.logged = new Subject<LogRecord>();
            this.count = 0;
        }

        public override void OnStart()
        {
            base.OnStart();

            DoSomethingLong();

            var timer = Observable.Timer(Period, Period)
                                  .Timestamp()
                                  .ObserveOn(SynchronizationContext.Current)
                                  .Subscribe(OnTimer);

            Handler.Disposables.Add(timer);
        }

        public override void OnStop()
        {
            base.OnStop();

            DoSomethingLong();
        }
        
        public override void Run()
        {
            while (Handler.TryHandleMessage(Timeout.InfiniteTimeSpan).CanNext())
            {
            }
        }

        private void OnTimer(Timestamped<long> info)
        {
            var s = string.Format("{0:yyyy/MM/dd HH:mm:ss.fff} {1}-Thread / OnTimer: {2} {3:yyyy/MM/dd HH:mm:ss.fff}",
                DateTime.Now, Thread.CurrentThread.Name, info.Value, info.Timestamp);
            Debug.WriteLine(s);

            //DoSomething();
        }

        private int LogCore(DateTime timestamp, string message)
        {
            var s = string.Format("{0:yyyy/MM/dd HH:mm:ss.fff} {1}-Thread / Log[{2}]: {3} {4:yyyy/MM/dd HH:mm:ss.fff}",
                DateTime.Now, Thread.CurrentThread.Name, count, message, timestamp);
            Debug.WriteLine(s);


            var record = new LogRecord(count, timestamp, message);
            DoSomething();

            count++;

            logged.OnNext(record);

            var ret = count;
            return ret;
        }

        public Task<int> Log(DateTime timestamp, string message)
        {
            var ret = Task.FromResult(LogCore(timestamp, message));
            return ret;
        }

        public async Task<int> LogDelayed(DateTime timestamp, string message, TimeSpan delay)
        {
            await Task.Delay(delay);

            var ret = LogCore(timestamp, message);
            return ret;
        }

        public Task LogStat()
        {
            var s = string.Format("{0:yyyy/MM/dd HH:mm:ss.fff} {1}-Thread / LogStat: Count={2}",
                DateTime.Now, Thread.CurrentThread.Name, count);
            Debug.WriteLine(s);

            DoSomething();

            var ret = Task.FromResult(Unit.Default);
            return ret;
        }

        public IObservable<LogRecord> Logged
        {
            get { return logged; }
        }
    }
}
